package com.tju.shortlink.admin.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.tju.shortlink.admin.common.biz.user.UserContext;
import com.tju.shortlink.admin.common.biz.user.UserInfoDTO;
import com.tju.shortlink.admin.common.biz.user.UserTokenDTO;
import com.tju.shortlink.admin.common.convention.exception.ClientException;
import com.tju.shortlink.admin.common.enums.UserErrorCodeEnum;
import com.tju.shortlink.admin.dao.entity.UserDo;
import com.tju.shortlink.admin.dao.mapper.UserMapper;
import com.tju.shortlink.admin.dto.req.UserLoginReqDTO;
import com.tju.shortlink.admin.dto.req.UserRegisterReqDTO;
import com.tju.shortlink.admin.dto.req.UserUpdateReqDTO;
import com.tju.shortlink.admin.dto.resp.UserLoginRespDTO;
import com.tju.shortlink.admin.dto.resp.UserRespDTO;
import com.tju.shortlink.admin.service.IGroupService;
import com.tju.shortlink.admin.service.IUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.net.ContentHandler;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.TimeUnit;

import static com.tju.shortlink.admin.common.constant.RedisCacheConstant.LOCK_USER_REGISTER_KEY;
import static com.tju.shortlink.admin.common.constant.RedisCacheConstant.USER_LOGIN_CACHE_KEY;
import static com.tju.shortlink.admin.common.enums.UserErrorCodeEnum.*;

/**
 * <p>
 *  服务实现类
 * </p>
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, UserDo> implements IUserService {

    private final RBloomFilter<String> userRegisterCachePenetrationBloomFilter;

    private final RedissonClient redissonClient;

    private final StringRedisTemplate redisTemplate;

    private final IGroupService groupService;

    @Override
    public UserRespDTO getUserByUsername(String username) {
        // 1. 校验
        if(StringUtils.isBlank(username)){
            throw new RuntimeException("请求参数不能为空");
        }
        // 2. 查询
        UserDo user = this.lambdaQuery()
                .eq(UserDo::getUsername, username).one();
        if(user == null){
            throw new ClientException(UserErrorCodeEnum.USER_NULL);
        }
        // 3. 封装
        UserRespDTO userRespDTO = new UserRespDTO();
        BeanUtils.copyProperties(user, userRespDTO);
        return userRespDTO;
    }

    @Override
    public Boolean hasUsername(String username) {
        // 1. 判断布隆过滤器中是否含有对应的用户名
        return !userRegisterCachePenetrationBloomFilter.contains(username);
    }

    @Override
    @Transactional
    public void Register(UserRegisterReqDTO requestParam) {
        // 1. 判断用户是否存在
        if(!hasUsername(requestParam.getUsername())){
            throw new ClientException(USER_NAME_EXIST);
        }
        // 2. 写入db
        // 获取分布式锁防止多个用户同时注册一个名称， 导致数据库瞬时压力过大
        RLock lock = redissonClient.getLock(LOCK_USER_REGISTER_KEY + requestParam.getUsername());
        if(!lock.tryLock()){
            throw new ClientException(USER_NAME_EXIST);
        }
        try {
            UserDo userDo = BeanUtil.toBean(requestParam, UserDo.class);
            System.out.println(userDo);
            int insert = baseMapper.insert(userDo);
            if (insert <= 0) {
                throw new ClientException(USER_SAVE_ERROR);
            }
            // 3. 存入布隆过滤器 并创建 默认分组
            groupService.saveGroup(userDo.getUsername(), "默认分组");
            userRegisterCachePenetrationBloomFilter.add(requestParam.getUsername());
        }catch (DuplicateKeyException ex){
            userRegisterCachePenetrationBloomFilter.add(requestParam.getUsername());
            throw new ClientException(USER_EXIST);
        }finally {
            lock.unlock();
        }
    }

    @Override
    public void update(UserUpdateReqDTO requestParam) {
        // 1. 校验用户名
        if(!UserContext.getUsername().equals(requestParam.getUsername())){
            throw new ClientException("用户名不能修改!");
        }
        // 2. 更新db
        LambdaUpdateWrapper<UserDo> wrapper = Wrappers.<UserDo>lambdaUpdate()
                .eq(UserDo::getUsername, UserContext.getUsername())
                .set(StringUtils.isNotBlank(requestParam.getPassword()), UserDo::getPassword, requestParam.getPassword())
                .set(StringUtils.isNotBlank(requestParam.getMail()), UserDo::getMail, requestParam.getMail())
                .set(StringUtils.isNotBlank(requestParam.getPhone()), UserDo::getPhone, requestParam.getPhone())
                .set(StringUtils.isNotBlank(requestParam.getRealName()), UserDo::getRealName, requestParam.getRealName());
        boolean update = this.update(wrapper);
        // 3. 更新失败处理
        if(!update){
            throw new ClientException(UserErrorCodeEnum.USER_UPDATE_ERROR);
        }
    }

    @Override
    public UserLoginRespDTO login(UserLoginReqDTO requestParam) {
        // 1. 查询redis用户是否存在
        if(hasUsername(requestParam.getUsername())){
            throw new ClientException(UserErrorCodeEnum.USER_NULL);
        }
        // 2. 检查登录状态
        String UserToken = redisTemplate.opsForValue().get(USER_LOGIN_CACHE_KEY + requestParam.getUsername());

        Random random = new Random();
        int randomTime = random.nextInt(60);
        if(StringUtils.isNotEmpty(UserToken)){
            // 获取解析用户信息
            UserTokenDTO userTokenDTO = JSON.parseObject(UserToken, UserTokenDTO.class);
            // 再次登录刷新有效期
            redisTemplate.expire(USER_LOGIN_CACHE_KEY+requestParam.getUsername(), (60 * 24)+randomTime, TimeUnit.MINUTES);
            String token = userTokenDTO.getToken();
            return new UserLoginRespDTO(token);
        }
        // 3.查询数据库
        UserDo user = lambdaQuery().eq(UserDo::getUsername, requestParam.getUsername())
                .eq(UserDo::getPassword, requestParam.getPassword())
                .eq(UserDo::getDelFlag, 0).one();
        if(user == null){
            throw new ClientException(UserErrorCodeEnum.USER_NULL);
        }
        // 4. 封装token
        String token = UUID.randomUUID().toString();
        UserInfoDTO userInfoDTO = BeanUtil.toBean(user, UserInfoDTO.class);
        userInfoDTO.setUserId(String.valueOf(user.getId()));
        UserTokenDTO userTokenDTO = UserTokenDTO.builder().token(token).userInfoDTO(userInfoDTO).build();

        // 5. 设置过期时间
        redisTemplate.opsForValue().set(USER_LOGIN_CACHE_KEY+requestParam.getUsername(), JSON.toJSONString(userTokenDTO));
        redisTemplate.expire(USER_LOGIN_CACHE_KEY+requestParam.getUsername(), (60 * 24)+randomTime, TimeUnit.MINUTES);
        return new UserLoginRespDTO(token);
    }

    @Override
    public Boolean checkLogin(String username, String token) {
        // 1. 获取token
        String UserToken = redisTemplate.opsForValue().get(USER_LOGIN_CACHE_KEY + username);
        if(StrUtil.isBlank(UserToken)){
            return Boolean.FALSE;
        }
        // 2. 解析对象并判断
        UserTokenDTO userTokenDTO = JSON.parseObject(UserToken, UserTokenDTO.class);
        if(userTokenDTO.getToken().equals(token)){
            return Boolean.TRUE;
        }
        return Boolean.FALSE;
    }

    @Override
    public void logout(String username, String token) {
        // 删除对应的token
        if(checkLogin(username, token)){
            redisTemplate.delete(USER_LOGIN_CACHE_KEY+username);
        }
        throw new ClientException("用户未登录");
    }


}
